<html>
   <meta charset="utf-8">
   <meta http-equiv="X-UA-Compatible" content="IE=edge">
   <meta name="viewport" content="width=device-width, initial-scale=1">
   <meta name="theme-color" content="#008000"/>
   <title>meshing_ml</title>
   <link rel="manifest" href="meshing_ml_manifest.json">
   <script>
    if('serviceWorker' in navigator) {
      navigator.serviceWorker.register('/meshing_ml_sw.js')
        .then(function() {
              console.log('Service Worker Registered');
        });
    }
   </script>
  <head>
    <style>
      body {
        margin: 0;
      }
    </style>
  </head>
  <body>
  <h1>meshing_ml</h1>
  <script src="three.js"></script>
  <script>
    let container, scene, camera, session;
    // let mesher = null;

    /* const localVector = new THREE.Vector3();
    const localVector2 = new THREE.Vector3();
    const localEuler = new THREE.Euler();
    const localMatrix = new THREE.Matrix4();
    const localFloat32Array = new Float32Array(16);

    const numCubes = 100;
    const cubeSize = 0.2;
    const cubeRange = 1;
    const cubeGeometry = new THREE.BoxBufferGeometry(1, 1, 1); */
    
    const terrainMeshes = [];
    const terrainMaterial = new THREE.ShaderMaterial({
      uniforms: {},
      vertexShader: `\
        attribute vec3 barycentric;
        varying vec3 vPosition;
        varying vec3 vBC;
        void main() {
          vBC = barycentric;
          vec4 modelViewPosition = modelViewMatrix * vec4(position, 1.0);
          vPosition = modelViewPosition.xyz;
          gl_Position = projectionMatrix * modelViewPosition;
        }
      `,
      fragmentShader: `\
        varying vec3 vPosition;
        varying vec3 vBC;

        vec3 color = vec3(0.984313725490196, 0.5490196078431373, 0.0);
        vec3 lightDirection = vec3(0.0, 0.0, 1.0);

        float edgeFactor() {
          vec3 d = fwidth(vBC);
          vec3 a3 = smoothstep(vec3(0.0), d*1.5, vBC);
          return min(min(a3.x, a3.y), a3.z);
        }

        void main() {
          float barycentricFactor = (0.2 + (1.0 - edgeFactor()) * 0.8);
          vec3 xTangent = dFdx( vPosition );
          vec3 yTangent = dFdy( vPosition );
          vec3 faceNormal = normalize( cross( xTangent, yTangent ) );
          float lightFactor = pow(dot(faceNormal, lightDirection), 2.0);
          gl_FragColor = vec4(color * barycentricFactor * lightFactor, 1.0);
        }
      `,
      polygonOffset: true,
      polygonOffsetFactor: -1,
      polygonOffsetUnits: -4,
    });
    const _getTerrainMesh = meshId => {
      let terrainMesh = terrainMeshes.find(terrainMesh => terrainMesh.meshId === meshId);
      if (!terrainMesh) {
        terrainMesh = _makeTerrainMesh(meshId);
        terrainMeshes.push(terrainMesh);
        scene.add(terrainMesh);
      }
      return terrainMesh;
    };
    const _makeTerrainMesh = meshId => {
      const geometry = new THREE.BufferGeometry();
      const material = terrainMaterial;

      const mesh = new THREE.Mesh(geometry, material);
      mesh.matrixAutoUpdate = false;
      mesh.frustumCulled = false;
      mesh.meshId = meshId;
      mesh.visible = false;
      return mesh;
    };
    const _loadTerrainMesh = (terrainMesh, {transformMatrix, positionArray, normalArray, indexArray}) => {
      if (indexArray.length > 0) {
        terrainMesh.matrix.fromArray(transformMatrix);
        terrainMesh.matrix.decompose(terrainMesh.position, terrainMesh.quaternion, terrainMesh.scale);
        terrainMesh.updateMatrixWorld(true);

        const {geometry} = terrainMesh;
        // const attributes = renderer.getAttributes();

        const positions = new Float32Array(indexArray.length*3);
        const barycentrics = new Float32Array(indexArray.length*3);
        for (let i = 0; i < indexArray.length; i += 3) {
          const ai = indexArray[i];
          const bi = indexArray[i+1];
          const ci = indexArray[i+2];

          const baseA = ai*3;
          const baseB = bi*3;
          const baseC = ci*3;

          const baseIndex = i*3;
          positions[baseIndex] = positionArray[baseA];
          positions[baseIndex+1] = positionArray[baseA+1];
          positions[baseIndex+2] = positionArray[baseA+2];
          positions[baseIndex+3] = positionArray[baseB];
          positions[baseIndex+4] = positionArray[baseB+1];
          positions[baseIndex+5] = positionArray[baseB+2];
          positions[baseIndex+6] = positionArray[baseC];
          positions[baseIndex+7] = positionArray[baseC+1];
          positions[baseIndex+8] = positionArray[baseC+2];

          barycentrics[baseIndex] = 1;
          barycentrics[baseIndex+1] = 0;
          barycentrics[baseIndex+2] = 0;
          barycentrics[baseIndex+3] = 0;
          barycentrics[baseIndex+4] = 1;
          barycentrics[baseIndex+5] = 0;
          barycentrics[baseIndex+6] = 0;
          barycentrics[baseIndex+7] = 0;
          barycentrics[baseIndex+8] = 1;
        }
        geometry.addAttribute('position', new THREE.BufferAttribute(positions, 3));
        geometry.addAttribute('barycentric', new THREE.BufferAttribute(barycentrics, 3));

        terrainMesh.visible = true;
      } else {
        terrainMesh.visible = false;
      }
    };
    const _removeTerrainMesh = terrainMesh => {
      scene.remove(terrainMesh);
      terrainMeshes.splice(terrainMeshes.indexOf(terrainMesh), 1);
      terrainMesh.geometry.dispose();
    };
    /* const _clearTerrainMeshes = () => {
      for (let i = 0; i < terrainMeshes.length; i++) {
        _removeTerrainMesh(terrainMeshes[i]);
      }
      terrainMeshes.length = 0;
    }; */
    const _meshadd = e => {
      console.log('mesh add', e.update.id);
      _loadTerrainMesh(_getTerrainMesh(e.update.id), e.update);
    };
    const _meshupdate = e => {
      console.log('mesh update', e.update.id);
      _loadTerrainMesh(_getTerrainMesh(e.update.id), e.update, e.update.positionArray.length, e.update.normalArray.length, e.update.indexArray.length);
    };
    const _meshremove = e => {
      console.log('mesh remove', e.update.id);
      const index = terrainMeshes.findIndex(terrainMesh => terrainMesh.meshId === e.update.id);
      if (index !== -1) {
        const terrainMesh = terrainMeshes[index];
        _removeTerrainMesh(terrainMesh);
      }
    };
    /* const _onMesh = e => {
      const {updates} = e;
      for (let i = 0; i < updates.length; i++) {
        const update = updates[i];
        const {id, type} = update;

        if (type === 'new' || type === 'update') {
          _loadTerrainMesh(_getTerrainMesh(id), update);
        } else if (type === 'unchanged') {
          // nothing
        } else {
          const index = terrainMeshes.findIndex(terrainMesh => terrainMesh.meshId === id);
          if (index !== -1) {
            const terrainMesh = terrainMeshes[index];
            _removeTerrainMesh(terrainMesh);
            terrainMeshes.splice(index, 1);
          }
        }
      }
    }; */

    function init() {
      container = document.createElement('div');
      document.body.appendChild(container);

      scene = new THREE.Scene();
      scene.matrixAutoUpdate = false;
      // scene.background = new THREE.Color(0x3B3961);

      camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
      // camera.position.set(0, 1, 0);
      // camera.lookAt(new THREE.Vector3());
      scene.add(camera);

      const ambientLight = new THREE.AmbientLight(0x808080);
      scene.add(ambientLight);

      const directionalLight = new THREE.DirectionalLight(0xFFFFFF, 1);
      directionalLight.position.set(1, 1, 1);
      scene.add(directionalLight);

      renderer = new THREE.WebGLRenderer({
        antialias: true,
        alpha: true,
      });
      renderer.setPixelRatio(window.devicePixelRatio);
      renderer.setSize(window.innerWidth, window.innerHeight);

      // window.browser.magicleap.RequestDepthPopulation(true);
      // renderer.autoClear = false;

      container.appendChild(renderer.domElement);

      renderer.setAnimationLoop(animate);
    }

    function animate(time, frame) {
      /* if (model) {
        const animationTime = 4000;
        const f = ((Date.now() % animationTime) / animationTime) * (Math.PI * 2);
        model.quaternion.setFromUnitVectors(
          new THREE.Vector3(0, 0, 1),
          new THREE.Vector3(Math.cos(f), 0, Math.sin(f)).normalize()
        );
        model.updateMatrixWorld();
      } */

      renderer.render(scene, renderer.vr.enabled ? renderer.vr.getCamera(camera) : camera);
    }

    init();

    (async () => {
      console.log('request session');
      session = await navigator.xr.requestSession({
        exclusive: true,
        extensions: {
          meshing: true,
        },
      }).catch(err => Promise.resolve(null));

      if (session) {
        // console.log('request first frame');
        session.requestAnimationFrame((timestamp, frame) => {
          renderer.vr.setSession(session, {
            frameOfReferenceType: 'stage',
          });

          const {views} = frame.getViewerPose();
          const viewport = session.baseLayer.getViewport(views[0]);
          const width = viewport.width;
          const height = viewport.height;

          renderer.setSize(width * 2, height);

          renderer.setAnimationLoop(null);

          renderer.vr.enabled = true;
          renderer.vr.setAnimationLoop(animate);

          session.addEventListener('meshadd', _meshadd);
          session.addEventListener('meshupdate', _meshupdate);
          session.addEventListener('meshremove', _meshremove);

          /* mesher = session.requestMeshing();
          mesher.onmesh = _onMesh; */

          console.log('running!');
        });
      } else {
        console.log('no xr devices');
      }
    })();
  </script>
  </body>
</html>
